- @title = 'Handling events'
:textile
  h3. Handling events
  
  Scripting web pages is primarily about handling events triggered by the user. These events include mouse events such
  as clicking or mousing over an element, keyboard events and document-wide events, such as detecting when the document
  has loaded. We use _event listener_ functions to respond to users' actions.
  
  There is nothing special about event listeners, they are just regular functions that you bind to a particular event
  so you can run some code whenever that event happens.
  
  h3. Required files
  
  * @http://yui.yahooapis.com/2.6.0/build/yahoo-dom-event/yahoo-dom-event.js@
  * @http://yui.yahooapis.com/2.6.0/build/selector/selector-beta.js@
  * @http://yoursite.com/ojay/js-class.js@
  * @http://yoursite.com/ojay/core.js@
  
  h3. How do I respond to events?
  
  Ojay makes this really simple, using the @on@ method. You can use this method to listen for any event on an Ojay
  collection as follows:
  
  <pre>
  Ojay('h3').on('click', function(element, e) {
      alert('You clicked on: ' + element.node.innerHTML);
  });</pre>
  
  Try clicking on one of the headings on this page to see this in action.
  
  @on@ takes two arguments: the event name (@click@, @mouseover@, etc) and a function that will fire whenever the event
  occurs. The function receives two arguments:
  
  * @element@ - an Ojay collection wrapping the single element that fired the event
  * @e@ - an @Event@ object, which can be used to stop default behaviour and get information about the event
  
  Note that the above code listens to all @h3@ tags in the document, but @element@ only refers to the one that fired
  the event.
  
  @on@ takes an optional third argument that specifies the meaning of the @this@ keyword inside the function. This
  becomes more useful when using classes and object oriented programming, but the following example should illustrate
  the idea:
  
  <pre>
  var firstPara = Ojay('p').at(0);
  
  Ojay('h1').on('click', function(element, e) {
      this.setStyle({color: 'red'});
  }, firstPara);</pre>
  
  If you click on the big heading at top of the page, you should see the first paragraph turn red.
  
  h3. Getting the event target
  
  While @element@ in your callback function refers to the member of the collection that triggered the event, that
  element may not be the event's target. For example, consider the following situation:
  
  <pre>
  <p>Lorem ipsum dolor sit amet, consectetuer adipiscing
  elit. Integer vehicula scelerisque est. <em>Vestibulum ante</em>
  ipsum primis in faucibus orci luctus et ultrices posuere
  cubilia Curae; Etiam tincidunt erat lobortis urna. Nam
  <strong>fermentum erat</strong> sed enim.</p>
  
  <script type="text/javascript">
    Ojay('p').on('click', function(element, e) {
      alert(element.node.tagName);
      alert(e.getTarget().node.tagName);
    });
  </script></pre>
  
  If you were to click on the paragraph, the first @alert()@ will always display "@P@". The second @alert()@ will
  depend on which child element of the paragraph you clicked on. It could display "@P@", "@EM@" or "@STRONG@"
  depending on where you click. This method can be used to set up event listeners for lots of elements just by
  listening to their common parent element. This registers fewer event listeners with the browser and thus uses
  a lot less memory.
  
  h3. Controlling event execution
  
  You'll often need to stop the browser running the default behaviour for an event, such as stopping it loading a
  new page when a particular link is clicked. Ojay gives you three methods on the event object for doing this.
  
  * @stopDefault@ stops the event's default behaviour from happening
  * @stopPropagate@ stops the event bubbling up the DOM
  * @stopEvent@ does both of the above
  
  An example:
  
  <pre>
  Ojay('#someLink').on('click', function(element, e) {
    e.stopDefault();
    Ojay.HTTP.GET(element.node.href).insertInto('#foo');
    // etc...
  })</pre>
  
  If all your callback does is stop the event, use one of Ojay's pre-stored callback functions to improve readability
  and avoid wasting memory on new functions. They have the same names as the event methods:
  
  <pre>
  Ojay('#someLink').on('click', Ojay.stopEvent)
      .setStyle({fontSize: '12px'})</pre>
  
  h3. Using @MethodChain@
  
  @on()@ is one of a few special methods in Ojay that return a "@MethodChain@":/articles/method_chain.html object.
  This means you can chain methods after the @on()@ call rather than using callback functions explicitly:
  
  <pre>
  // change H1 children's style when they are clicked
  Ojay('h1').on('click').children().setStyle({fontWeight: 'normal'})</pre>
  
  If you pass an object as the last argument to @on()@ in this situation, that object is used as the base of the
  chain:
  
  <pre>
  Ojay('a').on('click', Ojay.stopEvent, Ojay.HTTP)
      .GET('/index.html', {ajaxLayout: true})
      .insertInto('h1');</pre>
  
  See the "@MethodChain@":/articles/method_chain.html page for more information on how to use it.
  
  h3. How to delegate events
  
  Event delegation is the practise of getting a single DOM element to catch and handle events
  fired by any of its descendant elements. This technique can be used to minimize memory usage
  with bubbling events and allows event handlers to be applied to elements added to the document
  later through Ajax, since a fixed ancestor element can be used to catch events fired by its
  content even if the content changes. For more information, see this "article on icant.co.uk":http://icant.co.uk/sandbox/eventdelegation/.
  
  Ojay's event delegation is inspired by "Dan Webb's solution for jQuery":http://www.danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery,
  with a little added help for nested elements. To catch any clicks on a certain class of
  links, for example, you could do this:
  
  <pre>
  Ojay('a.external').on('click', ...)</pre>
  
  but having a separate listener on every element (as this example creates) wastes memory, and
  won't help you if any @a.external@ elements are added to the page after this JavaScript call.
  A better approach, using event delegation:
  
  <pre>
  Ojay('body').on('click', Ojay.delegateEvent({
    'a.external': function(link, evnt) {
      // ...
    }
  }));</pre>
  
  This creates a single listener (on the @body@ element) that catches all the clicks you want
  to listen to. Inside the callback, @link@ still refers to the event's target element - the
  clicked link - and not the @body@ element.
  
  You can specify more selectors to be handled by a single delegator by simply listing them:
  
  <pre>
  Ojay('body').on('click', Ojay.delegateEvent({
    'a.external': function(link, evnt) {
      // ...
    },
    
    '.content p': function(para, evnt) {
      // ...
    }
  }));</pre>
  
  For most purposes this works fine but say you have something like this:
  
  <pre>
  <a href="http://example.com" class="external">
      <img src="some-picture.png" />
  </a></pre>
  
  If someone clicks the link, the image will (more than likely) be the event target, not the
  anchor element. You could work around this by doing this:
  
  <pre>
  Ojay('body').on('click', Ojay.delegateEvent({
    'a.external, a.external *': function(element, evnt) {
      // ...
    }
  }));</pre>
  
  This would work, but then if you want to refer to the link you need to find out whether the
  link or one of its children was clicked and do a bit of tree crawling to find the element
  you're interested in. To make it easier, you can pass @true@ as a second parameter to
  @delegateEvent()@ and it will crawl back up the DOM tree until it finds a matching element.
  
  <pre>
  Ojay('body').on('click', Ojay.delegateEvent({
    'a.external': function(link, evnt) {
      // ...
    }
  }, true));</pre>
  
  This will catch clicks on @a.external@ elements, and _any elements contained within them_.
  The @link@ argument will refer to the @a.external@ element, rather than to the direct event
  target.
  
  It should be noted that you do not necessarily need to use the @body@ tag for event delegation,
  just any permanent element that contains the elements you want to monitor.
  

%script{:type => 'text/javascript'}
  :plain
    Ojay('h3').on('click', function(element, e) {
        alert('You clicked on: ' + element.node.innerHTML);
    });
    
    var firstPara = Ojay('p').at(0);
    Ojay('h1').on('click', function(element, e) {
        this.setStyle({color: 'red'});
    }, firstPara);
